export { generateVirtualFileGlobalEntryWithOldDesign }

// TO-DO/next-major-release:
//  - Remove this file
//    - Instead only generate generateVirtualFileGlobalEntry()
//  - Remove old `.page.js`/`.page.client.js`/`.page.server.js` interface
//    - Systematically remove all pageFilesAll references does the trick?

import type { ResolvedConfig } from 'vite'
import {
  assert,
  assertPosixPath,
  scriptFileExtensionPattern,
  debugGlob,
  isVersionMatch,
  assertWarning,
} from '../../utils.js'
import { parseVirtualFileId } from '../../../../shared-server-node/virtualFileId.js'
import { version as viteVersion } from 'vite'
import { type FileType, fileTypes } from '../../../../shared-server-client/getPageFiles/fileTypes.js'
import path from 'node:path'
import { generateVirtualFileGlobalEntry } from './generateVirtualFileGlobalEntry.js'
import { getVikeConfigInternal, isV1Design as isV1Design_ } from '../../shared/resolveVikeConfigInternal.js'
import { getOutDirs } from '../../shared/getOutDirs.js'
import { isViteServerSide_extraSafe } from '../../shared/isViteServerSide.js'
import { resolveIncludeAssetsImportedByServer } from '../../../../server/runtime/renderPageServer/getPageAssets/retrievePageAssetsProd.js'
import type { Environment } from 'vite'
import { VIRTUAL_FILE_ID_constantsGlobalThis } from '../pluginReplaceConstantsGlobalThis.js'

type GlobRoot = {
  includeDir: string // slash-terminated
  excludeDir?: string // slash-terminated, no leading exclamation mark
}

async function generateVirtualFileGlobalEntryWithOldDesign(
  id: string,
  options: { ssr?: boolean } | undefined,
  config: ResolvedConfig,
  env: Environment,
  isDev: boolean,
) {
  const idParsed = parseVirtualFileId(id)
  assert(idParsed && idParsed.type === 'global-entry')
  const { isForClientSide, isClientRouting } = idParsed
  assert(isForClientSide === !isViteServerSide_extraSafe(config, env, options))
  const code = await getCode(config, isForClientSide, isClientRouting, isDev, id)
  return code
}

async function getCode(
  config: ResolvedConfig,
  isForClientSide: boolean,
  isClientRouting: boolean,
  isDev: boolean,
  id: string,
) {
  const { command } = config
  assert(command === 'serve' || command === 'build')
  const isBuild = command === 'build'
  assert(isDev === !isBuild)
  let content = ''

  if (!isForClientSide) {
    content += `import '${VIRTUAL_FILE_ID_constantsGlobalThis}';\n`
  }

  {
    const globRoots = getGlobRoots(config)
    debugGlob('Glob roots: ', globRoots)
    content += await generateGlobImports(globRoots, isBuild, isForClientSide, isClientRouting, isDev, id)
  }
  debugGlob(`Glob imports for ${isForClientSide ? 'client' : 'server'}:\n`, content)
  return content
}

function determineInjection({
  fileType,
  isForClientSide,
  isClientRouting,
  isPrerendering,
  isBuild,
}: {
  fileType: FileType
  isForClientSide: boolean
  isClientRouting: boolean
  isPrerendering: boolean
  isBuild: boolean
}): { includeImport: boolean; includeExportNames: boolean } {
  if (!isForClientSide) {
    return {
      includeImport: fileType === '.page.server' || fileType === '.page' || fileType === '.page.route',
      includeExportNames:
        isPrerendering && isBuild
          ? fileType === '.page.client' || fileType === '.page.server' || fileType === '.page' // We extensively use `PageFile['exportNames']` while pre-rendering, in order to avoid loading page files unnecessarily, and therefore reducing memory usage.
          : fileType === '.page.client',
    }
  } else {
    const includeImport = fileType === '.page.client' || fileType === '.css' || fileType === '.page'
    if (!isClientRouting) {
      return {
        includeImport,
        includeExportNames: false,
      }
    } else {
      return {
        includeImport: includeImport || fileType === '.page.route',
        includeExportNames: fileType === '.page.client' || fileType === '.page.server' || fileType === '.page',
      }
    }
  }
}

async function generateGlobImports(
  globRoots: GlobRoot[],
  isBuild: boolean,
  isForClientSide: boolean,
  isClientRouting: boolean,
  isDev: boolean,
  id: string,
) {
  let fileContent = `// Generated by Vike

export const pageFilesLazy = {};
export const pageFilesEager = {};
export const pageFilesExportNamesLazy = {};
export const pageFilesExportNamesEager = {};
export const pageFilesList = [];
export const neverLoaded = {};

${await generateVirtualFileGlobalEntry(isForClientSide, isDev, id, isClientRouting)}

`

  // We still use import.meta.glob() when using th V1 design in order to not break the V1 design deprecation warning
  const isV1Design = isV1Design_()

  const vikeConfig = await getVikeConfigInternal()

  // Old design => no + files => only to enable pre-rendering is setting `vike({prerender})` in vite.config.js
  const isPrerendering = !!vikeConfig.config.prerender
  fileTypes
    .filter((fileType) => fileType !== '.css')
    .forEach((fileType) => {
      const { includeImport, includeExportNames } = determineInjection({
        fileType,
        isForClientSide,
        isClientRouting,
        isPrerendering,
        isBuild,
      })
      if (includeImport) {
        fileContent += getGlobs(globRoots, isBuild, fileType, null, isV1Design)
      }
      if (includeExportNames) {
        fileContent += getGlobs(globRoots, isBuild, fileType, 'extractExportNames', isV1Design)
      }
    })
  const includeAssetsImportedByServer = resolveIncludeAssetsImportedByServer(vikeConfig.config)
  if (includeAssetsImportedByServer && isForClientSide) {
    fileContent += getGlobs(globRoots, isBuild, '.page.server', 'extractAssets', isV1Design)
  }

  return fileContent
}

type PageFileVar =
  | 'pageFilesLazy'
  | 'pageFilesEager'
  | 'pageFilesExportNamesLazy'
  | 'pageFilesExportNamesEager'
  | 'neverLoaded'

function getGlobs(
  globRoots: GlobRoot[],
  isBuild: boolean,
  fileType: Exclude<FileType, '.css'>,
  query: 'extractExportNames' | 'extractAssets' | null,
  isV1Design: boolean,
): string {
  const isEager = isBuild && (query === 'extractExportNames' || fileType === '.page.route')

  let pageFilesVar: PageFileVar
  if (query === 'extractExportNames') {
    if (!isEager) {
      pageFilesVar = 'pageFilesExportNamesLazy'
    } else {
      pageFilesVar = 'pageFilesExportNamesEager'
    }
  } else if (query === 'extractAssets') {
    assert(!isEager)
    pageFilesVar = 'neverLoaded'
  } else if (!query) {
    if (!isEager) {
      pageFilesVar = 'pageFilesLazy'
    } else {
      // Used for `.page.route.js` files
      pageFilesVar = 'pageFilesEager'
    }
  } else {
    assert(false)
  }

  const varNameSuffix =
    (fileType === '.page' && 'Isomorph') ||
    (fileType === '.page.client' && 'Client') ||
    (fileType === '.page.server' && 'Server') ||
    (fileType === '.page.route' && 'Route')
  assert(varNameSuffix)
  const varName = `${pageFilesVar}${varNameSuffix}`

  const varNameLocals: string[] = []
  return [
    ...globRoots.map((globRoot, i) => {
      const varNameLocal = `${varName}${i + 1}`
      varNameLocals.push(varNameLocal)
      const globIncludePath = `'${getGlobPath(globRoot.includeDir, fileType)}'`
      const globExcludePath = globRoot.excludeDir ? `'!${getGlobPath(globRoot.excludeDir, fileType)}'` : null
      const globOptions: Record<string, unknown> = { eager: isEager }
      if (query) {
        const isNewViteInterface = isVersionMatch(viteVersion, ['5.1.0'])
        if (
          isNewViteInterface &&
          // When used for the old design, the new syntax breaks Vike's CI (surprinsigly so). I couldn't reproduce locally (I didn't dig much).
          isV1Design
        ) {
          globOptions.query = `?${query}`
        } else {
          globOptions.as = query
          const msg = [
            "Update to the new V1 design to get rid of Vite's warning:",
            'The glob option "as" has been deprecated in favour of "query".',
            'See https://vike.dev/migration/v1-design for how to migrate.',
          ].join(' ')
          assertWarning(!isNewViteInterface, msg, { onlyOnce: true })
        }
      }
      const globPaths = globExcludePath ? `[${globIncludePath}, ${globExcludePath}]` : `[${globIncludePath}]`
      const globLine = `const ${varNameLocal} = import.meta.glob(${globPaths}, ${JSON.stringify(globOptions)});`
      return globLine
    }),
    `const ${varName} = {${varNameLocals.map((varNameLocal) => `...${varNameLocal}`).join(',')}};`,
    `${pageFilesVar}['${fileType}'] = ${varName};`,
    '',
  ].join('\n')
}

function getGlobRoots(config: ResolvedConfig): GlobRoot[] {
  const globRoots: GlobRoot[] = [
    {
      includeDir: '/',
      excludeDir: path.posix.relative(config.root, getOutDirs(config, undefined).outDirRoot),
    },
  ]
  return globRoots
}

function getGlobPath(globRootDir: string, fileType: FileType): string {
  assertPosixPath(globRootDir)
  let globPath = [...globRootDir.split('/'), '**', `*${fileType}.${scriptFileExtensionPattern}`]
    .filter(Boolean)
    .join('/')
  if (!globPath.startsWith('/')) {
    globPath = '/' + globPath
  }
  return globPath
}
